package v1

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/google/cel-go/cel"
	"github.com/pkg/errors"
	expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"

	"quipus/internal/database/model"
	"quipus/internal/utils"
	v1pb "quipus/proto/gen/api/v1"
	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"
)

func (s *APIV1Service) ListTenants(ctx context.Context, request *v1pb.ListTenantsRequest) (*v1pb.ListTenantsResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	tenantFind := &apb.FindTenant{}
	if request.Filter != "" {
		filter, err := parseSearchTenantsFilter(request.Filter)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
		}

		if filter.TenantName != nil {
			tenantFind.TenantName = *filter.TenantName
		}
		if filter.Random {
			tenantFind.Random = true
		}
		if filter.Limit != nil {
			tenantFind.Limit = int32(*filter.Limit)
		}
		if filter.Limit != nil {
			tenantFind.Limit = int32(*filter.Limit)
		}

		if filter.TenantUrn != nil {
			tenantID, err := utils.ExtractIDFromUrn(utils.TenantUrnPrefix, *filter.TenantUrn)
			if err != nil {
				return nil, status.Errorf(codes.InvalidArgument, "invalid tenant urn: %v", err)
			}

			if currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
				if tenantID != currentUser.TenantID {
					return nil, status.Errorf(codes.PermissionDenied, "permission denied")
				}
			}
			tenantFind.Id = tenantID
		}
	}

	var limit, offset int32
	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}

	if limit <= 0 {
		limit = DefaultPageSize
	}
	tenantFind.Limit = limit
	tenantFind.Offset = offset
	tenants, totalSize, err := s.Gds.Handler.ListTenants(ctx, tenantFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
	}

	response := &v1pb.ListTenantsResponse{
		TotalSize: int32(totalSize),
		Tenants:   []*v1pb.Tenant{},
	}

	for _, tenant := range tenants {
		response.Tenants = append(response.Tenants, convertTenantFromStore(tenant))
	}
	return response, nil
}

func (s *APIV1Service) CreateTenant(ctx context.Context, request *v1pb.CreateTenantRequest) (*v1pb.Tenant, error) {
	// currentUser, err := s.GetCurrentUser(ctx)
	// if err != nil {
	// 	return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	// }
	// if currentUser.Role != cpb.Role_HOST.String() || currentUser.Role != cpb.Role_ADMIN.String() {
	// 	return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	// }
	if !utils.UIDMatcher.MatchString(strings.ToLower(request.TenantName)) {
		return nil, status.Errorf(codes.InvalidArgument, "invalid tenant name: %s", request.TenantName)
	}

	tenant := &model.Tenant{
		TenantName:  request.TenantName,
		ShowName:    request.ShowName,
		Email:       request.Email,
		AvatarURL:   request.Avatar,
		Description: request.Description,
		RowStatus:   cpb.RowStatus_ACTIVE.String(),
	}

	err := s.Gds.Handler.CreateTenant(ctx, tenant)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
	}

	return convertTenantFromStore(tenant), nil
}

func (s *APIV1Service) UpdateTenant(ctx context.Context, request *v1pb.UpdateTenantRequest) (*v1pb.Tenant, error) {
	tenantId, err := utils.ExtractIDFromUrn(utils.TenantUrnPrefix, request.Tenant.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
	}

	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	if currentUser.TenantID != tenantId && currentUser.Role != cpb.Role_ADMIN.String() && currentUser.Role != cpb.Role_HOST.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	if currentUser.TenantID == tenantId && currentUser.Role != cpb.Role_TENANT_ADMIN.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "update mask is empty")
	}

	tenant, err := s.Gds.Handler.GetTenant(ctx, &apb.FindTenant{Id: tenantId})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get tenant: %v", err)
	}
	if tenant == nil {
		return nil, status.Errorf(codes.NotFound, "tenant not found")
	}

	currentTs := time.Now().Unix()
	update := &apb.UpdateTenant{
		Id:        tenant.ID,
		UpdatedTs: currentTs,
	}

	for _, field := range request.UpdateMask.Paths {
		if field == "tenant_name" {
			if !utils.UIDMatcher.MatchString(strings.ToLower(request.Tenant.TenantName)) {
				return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.Tenant.TenantName)
			}
			update.TenantName = request.Tenant.TenantName
		} else if field == "show_name" {
			update.ShowName = request.Tenant.ShowName
		} else if field == "email" {
			update.Email = request.Tenant.Email
		} else if field == "avatar_url" {
			update.Avatar = request.Tenant.Avatar
		} else if field == "description" {
			update.Description = request.Tenant.Description
		} else if field == "row_status" {
			update.RowStatus = request.Tenant.RowStatus
		} else {
			return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
		}
	}

	_, err = s.Gds.Handler.UpdateTenant(ctx, update)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to update user: %v", err)
	}

	tenantUpdated, err := s.Gds.Handler.GetTenant(ctx, &apb.FindTenant{Id: tenant.ID})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	return convertTenantFromStore(tenantUpdated), nil
}

func (s *APIV1Service) DeleteTenant(ctx context.Context, request *v1pb.DeleteTenantRequest) (*emptypb.Empty, error) {
	tenantId, err := utils.ExtractIDFromUrn(utils.TenantUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid tenant urn: %v", err)
	}
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	if currentUser.TenantID != tenantId && currentUser.Role != cpb.Role_ADMIN.String() && currentUser.Role != cpb.Role_HOST.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	if currentUser.TenantID == tenantId && currentUser.Role != cpb.Role_TENANT_ADMIN.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	tenant, err := s.Gds.Handler.GetTenant(ctx, &apb.FindTenant{Id: tenantId})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	if tenant == nil {
		return nil, status.Errorf(codes.NotFound, "user not found")
	}

	if err := s.Gds.Handler.DeleteTenant(ctx, &apb.DeleteTenant{
		Id: tenant.ID,
	}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
	}

	return &emptypb.Empty{}, nil
}

func convertTenantFromStore(tenant *model.Tenant) *v1pb.Tenant {
	tenantpb := &v1pb.Tenant{
		Urn:         utils.BuildUrn(utils.TenantUrnPrefix, tenant.ID),
		Id:          tenant.ID,
		RowStatus:   cpb.RowStatus(cpb.RowStatus_value[tenant.RowStatus]),
		CreateTime:  timestamppb.New(time.Unix(tenant.CreatedTs, 0)),
		UpdateTime:  timestamppb.New(time.Unix(tenant.UpdatedTs, 0)),
		TenantName:  tenant.TenantName,
		ShowName:    tenant.ShowName,
		Avatar:      tenant.AvatarURL,
		Description: tenant.Description,
	}
	// Use the avatar URL instead of raw base64 image data to reduce the response size.
	if tenant.AvatarURL != "" {
		if isSimpleFileName(tenant.AvatarURL) {
			tenantpb.Avatar = fmt.Sprintf("/file/%s/avatar", tenantpb.Urn)
		}

	}
	return tenantpb
}

// SearchTenantsFilterCELAttributes are the CEL attributes for SearchUsersFilter.
var SearchTenantsFilterCELAttributes = []cel.EnvOption{
	cel.Variable("tenant_name", cel.StringType),
	cel.Variable("random", cel.BoolType),
	cel.Variable("limit", cel.IntType),
	cel.Variable("tenant", cel.StringType),
}

type SearchTenantsFilter struct {
	TenantName *string
	Random     bool
	Limit      *int
	TenantUrn  *string
}

func parseSearchTenantsFilter(expression string) (*SearchTenantsFilter, error) {
	e, err := cel.NewEnv(SearchTenantsFilterCELAttributes...)
	if err != nil {
		return nil, err
	}
	ast, issues := e.Compile(expression)
	if issues != nil {
		return nil, errors.Errorf("found issue %v", issues)
	}
	filter := &SearchTenantsFilter{}
	expr, err := cel.AstToParsedExpr(ast)
	if err != nil {
		return nil, err
	}
	callExpr := expr.GetExpr().GetCallExpr()
	findSearchTenantsField(callExpr, filter)
	return filter, nil
}

func findSearchTenantsField(callExpr *expr.Expr_Call, filter *SearchTenantsFilter) {
	if len(callExpr.Args) == 2 {
		idExpr := callExpr.Args[0].GetIdentExpr()
		if idExpr != nil {
			if idExpr.Name == "tenant_name" {
				tenantName := callExpr.Args[1].GetConstExpr().GetStringValue()
				filter.TenantName = &tenantName
			} else if idExpr.Name == "random" {
				random := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.Random = random
			} else if idExpr.Name == "limit" {
				limit := int(callExpr.Args[1].GetConstExpr().GetInt64Value())
				filter.Limit = &limit
			} else if idExpr.Name == "tenant" {
				urn := callExpr.Args[1].GetConstExpr().GetStringValue()
				filter.TenantUrn = &urn
			}
			return
		}
	}
	for _, arg := range callExpr.Args {
		callExpr := arg.GetCallExpr()
		if callExpr != nil {
			findSearchTenantsField(callExpr, filter)
		}
	}
}
